using System;
using System.Collections.Generic;
using System.Reflection;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Ext.Beans
{
    /**
     * @author Attila Szegedi
     * @version $Id: $
     */
    class OverloadedVarArgMethod : OverloadedMethod
    {
        private static readonly Dictionary<ArgumentPacker, ArgumentPacker> CanoncialArgPackers = new Dictionary<ArgumentPacker, ArgumentPacker>();

        private readonly Dictionary<MemberInfo, ArgumentPacker> _argPackers = new Dictionary<MemberInfo, ArgumentPacker>();
        
        private class ArgumentPacker
        {
            private readonly int _argCount;
            private readonly Type _varArgType;
            
            internal ArgumentPacker(Type[] argTypes)
            {
                _argCount = argTypes.Length;
                //_varArgType = argTypes[_argCount - 1].getComponentType(); 
                throw new NotImplementedException();
            }

            internal object[] PackArgs(object[] args, IList<ITemplateModel> modelArgs, BeansWrapper w) 
            {
                int actualArgCount = args.Length;
                int fixArgCount = _argCount - 1;
                if (args.Length != _argCount)
                {
                    var newargs = new object[_argCount];
                    Array.Copy(args, 0, newargs, 0, fixArgCount);
                    object array = Array.CreateInstance(_varArgType, actualArgCount - fixArgCount);
                    for (int i = fixArgCount; i < actualArgCount; ++i)
                    {
                        object val = w.Unwrap(modelArgs[i], _varArgType);
                        if (val == BeansWrapper.CanNotUnwrap)
                        {
                            return null;
                        }
                        ((Array) array).SetValue(val, i - fixArgCount);
                    }
                    newargs[fixArgCount] = array;
                    return newargs;
                }
                else
                {
                    object val = w.Unwrap(modelArgs[fixArgCount], _varArgType);
                    if (val == BeansWrapper.CanNotUnwrap)
                    {
                        return null;
                    }
                    object array = Array.CreateInstance(_varArgType, 1);
                    ((Array) array).SetValue(val, 0);
                    args[fixArgCount] = array;
                    return args;
                }
            }
            
            public override bool Equals(object obj)
            {
                if (obj is ArgumentPacker)
                {
            	    var p = (ArgumentPacker)obj;
            	    return _argCount == p._argCount && _varArgType == p._varArgType;
                }
                return false;
            }
            
            public override int GetHashCode()
            {
                return _argCount ^ _varArgType.GetHashCode();
            }
        }

        protected override void OnAddSignature(MemberInfo member, Type[] argTypes)
        {
    	    var argPacker = new ArgumentPacker(argTypes);
    	    lock (CanoncialArgPackers)
            {
    	        ArgumentPacker canonical = CanoncialArgPackers.GetValueOrDefault(argPacker);
    	        if (canonical == null)
                {
        	        CanoncialArgPackers.Add(argPacker, argPacker);
    	        }
    	        else
                {
    	            argPacker = canonical;
    	        }
    	    }
            _argPackers.Add(member, argPacker);
            ComponentizeLastType(argTypes);
        }

        protected override void UpdateSignature(int l)
        {
    	    Type[][] marshalTypes = GetMarshalTypes();
    	    Type[] newTypes = marshalTypes[l];
            // First vararg marshal type spec with less parameters than the 
            // current spec influences the types of the current marshal spec.
            for (int i = l; i-- > 0;)
            {
                Type[] previousTypes = marshalTypes[i];
                if (previousTypes != null)
                {
                    VarArgUpdate(newTypes, previousTypes);
                    break;
                }
            }
            // Vararg marshal spec with exactly one parameter more than the current
            // spec influences the types of the current spec
            if (l + 1 < marshalTypes.Length)
            {
                Type[] oneLongerTypes = marshalTypes[l + 1];
                if (oneLongerTypes != null)
                {
                    VarArgUpdate(newTypes, oneLongerTypes);
                }
            }
        }

        protected override void AfterSignatureAdded(int l)
        {
    	    // Since this member is vararg, its types influence the types in all
            // type specs longer than itself.
    	    Type[][] marshalTypes = GetMarshalTypes();
            Type[] newTypes = marshalTypes[l];
            for (int i = l + 1; i < marshalTypes.Length; ++i)
            {
                Type[] existingTypes = marshalTypes[i];
                if (existingTypes != null)
                {
                    VarArgUpdate(existingTypes, newTypes);
                }
            }
            // It also influences the types in the marshal spec that is exactly
            // one argument shorter (as vararg methods can be invoked with 0
            // variable arguments, that is, with k-1 cardinality).
            if (l > 0)
            {
                Type[] oneShorterTypes = marshalTypes[l - 1];
                if (oneShorterTypes != null)
                {
                    VarArgUpdate(oneShorterTypes, newTypes);
                }
            }
        }
        
        private static void VarArgUpdate(Type[] modifiedTypes, Type[] modifyingTypes)
        {
            int dl = modifiedTypes.Length;
            int gl = modifyingTypes.Length;
            int min = Math.Min(gl, dl);
            for (int i = 0; i < min; ++i)
            {
                modifiedTypes[i] = MethodUtilities.GetMostSpecificCommonType(modifiedTypes[i], 
                        modifyingTypes[i]);
            }
            if (dl > gl)
            {
                Type varArgType = modifyingTypes[gl - 1];
                for(int i = gl; i < dl; ++i) {
                    modifiedTypes[i] = MethodUtilities.GetMostSpecificCommonType(modifiedTypes[i], 
                            varArgType);
                }
            }
        }
        
        private static void ComponentizeLastType(Type[] types)
        {
            int l1 = types.Length - 1;
            //assert l1 >= 0;
            //assert types[l1].isArray();
            //types[l1] = types[l1].getComponentType();
            throw new NotImplementedException();
        }

        internal override object GetMemberAndArguments(IList<ITemplateModel> arguments, BeansWrapper w) 
        {
    //        if (arguments == null)
    //        {
    //            // null is treated as empty args
    //            arguments = Collections.EMPTY_LIST;
    //        }
    //        int l = arguments.Count;
    //        Type[][] marshalTypes = GetMarshalTypes();
    //        var args = new object[l];
    //        // Starting from args.length + 1 as we must try to match against a case
    //        // where all specified args are fixargs, and the vararg portion 
    //        // contains zero args
    //outer:  for (int j = Math.Min(l + 1, marshalTypes.Length - 1); j >= 0; --j)
    //        {
    //            Type[] types = marshalTypes[j];
    //            if (types == null)
    //            {
    //                if (j == 0)
    //                {
    //                    return NoSuchMethod;
    //                }
    //                continue;
    //            }
    //            // Try to marshal the arguments
    //            Iterator it = arguments.iterator();
    //            for (int i = 0; i < l; ++i)
    //            {
    //                object dst = w.Unwrap((ITemplateModel)it.Next(), i < j ? types[i] : types[j - 1]);
    //                if (dst == BeansWrapper.CanNotUnwrap)
    //                {
    //                    continue outer;
    //                }
    //                if (dst != args[i])
    //                {
    //                    args[i] = dst;
    //                }
    //            }
    //            break;
    //        }
            
    //        object objMember = GetMemberForArgs(args, true);
    //        if (objMember is MemberInfo)
    //        {
    //            var member = (MemberInfo) objMember;
    //            args = (_argPackers.GetValueOrDefault(member)).PackArgs(args, arguments, w);
    //            if (args == null)
    //            {
    //                return NoSuchMethod;
    //            }
    //            BeansWrapper.CoerceBigDecimals(GetSignature(member), args);
    //            return new MemberAndArguments(member, args);
    //        }
    //        return objMember; // either NOT_FOUND or AMBIGUOUS

            throw new NotImplementedException();
        }
    }
}